package com.zkh.myutils.scheduled;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.json.JSONParseException;
import com.zkh.myutils.scheduled.cron.CronBean;
import com.zkh.myutils.scheduled.cron.CronParseException;
import com.zkh.myutils.system.ConfigLoader;
import com.zkh.myutils.utils.PathUtils;
import com.zkh.myutils.utils.StringUtils;

/**
 * 定时任务扫描器
 * @author zkh
 */
public class ScheduledTaskScanner {
	
	/**
	 * 定时任务配置
	 */
	private static Map<String, Object> config = ConfigLoader.getMap("scheduled", new HashMap<>());
	/**
	 * 定时任务表前缀
	 */
	private static String taskRecordTablePre;
	/**
	 * 任务Bean配置
	 */
	private static List<ScheduledTaskConfig> taskConfigList = new ArrayList<>();
	/**
	 * 初始化线程池
	 */
	private static ExecutorService threadPool = Executors.newFixedThreadPool(50);
	/**
	 * 当前任务
	 */
	private static ScheduledFuture<?> scheduledFuture;
	/**
	 * 使用数据库标识
	 */
	private static boolean useDatabase;
	/**
	 * 计毫秒。因为误差，可能会存在某一秒遗漏从而导致任务不执行（概率极小），这里通过变量递增保证每一秒都不会遗漏
	 */
	private static long millisecond;
	/**
	 * 启动标识
	 */
	private static boolean runFlag = false;
	
	/**
	 * 获取任务配置
	 */
	public static ScheduledTaskConfig geTaskConfig(String jobEcd) {
		//遍历查询
		for(ScheduledTaskConfig config: taskConfigList) {
			//匹配成功
			if(jobEcd.equals(config.getJobEcd())) {
				return config;
			}
		}
		return null;
	}
	
	/**
	 * 检查并开始运行，定时任务入口
	 * @param classLoader 类加载器，将自动加载定时任务
	 */
	public static synchronized void checkRun(ClassLoader classLoader) {
		checkRun(classLoader, new HashMap<>());
	}
	
	/**
	 * 检查并开始运行
	 * @param taskBeanMap 任务Bean列表。key为Bean的名称。用于classLoader加载不到任务bean的场景
	 */
	public static synchronized void checkRun(Map<String, ScheduledTask> taskBeanMap) {
		checkRun(null, taskBeanMap);
	}
	
	/**
	 * 检查并开始运行，定时任务入口。首先使用类加载器加载定时任务Bean，再用传入的定时任务Bean覆盖已加载的定时任务Bean。
	 * @param classLoader 类加载器，将自动加载定时任务
	 * @param taskBeanMap 任务Bean列表。key为Bean的名称。用于classLoader加载不到任务bean的场景
	 */
	public static void checkRun(ClassLoader classLoader, Map<String, ScheduledTask> taskBeanMap) {
		//已经运行
		if(runFlag) {
			Logger.warn("定时任务 - 定时任务扫描器已运行，本次调用自动忽略");
		}else {
			//开启，则运行
			if("true".equals(config.get("open"))) {
				//输出日志
				Logger.info("定时任务 - 定时任务配置为开启，开始处理定时任务");
				/** 处理基本参数 **/
				//任务Bean地址并加载
				Map<String, ScheduledTask> nameTaskMap = new HashMap<>();
				//有加载器，则加载
				if(classLoader!=null) {
					nameTaskMap.putAll(loadTaskBean(classLoader, (String) config.get("taskLocation")));;
				}
				//任务Bean列表
				if(taskBeanMap!=null && !taskBeanMap.isEmpty()) {
					nameTaskMap.putAll(taskBeanMap);
				}
				//配置为空
				if(nameTaskMap.isEmpty()) {
					//日志
					Logger.info("定时任务 - 没有扫描到定时任务Bean，定时任务不启用");
					return;
				}
				//是否使用数据
				useDatabase = "true".equals(config.get("useDatabase"));
				//数据库操作类
				ScheduledTaskService service = useDatabase ? new ScheduledTaskService() : null;
				//初始化数据库
				if(useDatabase){
					//日志
					Logger.info("定时任务 - 启用数据库，开始初始化数据表");
					//初始化数据表
					service.createTable();
				}
				/** 加载配置的任务 **/
				if(!loadTaskConfig(nameTaskMap)){
					//日志
					Logger.info("定时任务 - 没有加载到定时任务配置信息，定时任务不启用");
					return;
				}
				//将配置任务保存到数据库
				if(service!=null) {
					service.saveTaskDef(taskConfigList);
				}
				/** 下面开始启动定时任务 **/
				//启动标识
				runFlag = true;
				//初始毫秒
				millisecond = System.currentTimeMillis();
				//添加扫描器定时任务
				scheduledFuture = ScheduledExecutor.addTask(()->{
					//获取Cron格式时间
					int[] timeParts = CronBean.getTimeParts(millisecond+=1000);
					//提交任务到线程池
					threadPool.submit(()->runCore(timeParts, service));
				}, 1000, 1000);
			}
		}
	}
	
	/**
	 * 获取表前缀
	 */
	public static String getTaskRecordTablePre() {
		//未初始化，则进行初始化
		if(taskRecordTablePre==null) {
			//处理表前缀
			String tablePre = (String) config.get("taskRecordTablePre");
			//赋值
			if(StringUtils.isNotEmpty(tablePre)) {
				//保存表前缀
				taskRecordTablePre = tablePre.endsWith("_") ? tablePre : tablePre + "_";
			}else {
				taskRecordTablePre = "";
			}
		}
		//返回表前缀
		return taskRecordTablePre;
	}
	
	/**
	 * 取消定时任务
	 */
	public static void cancleScheduledTask() {
		//有定时任务
		if(scheduledFuture!=null) {
			//关闭
			scheduledFuture.cancel(true);
			//关闭线程池
			threadPool.shutdownNow();
			//线程池清除
			ScheduledExecutor.shutdownIfAllCancelled(10);
			//销毁
			scheduledFuture = null;
			//重置启动标识
			runFlag = false;
		}
	}
	
	/**
	 * 加载任务配置
	 */
	@SuppressWarnings("unchecked")
	private static boolean loadTaskConfig(Map<String, ScheduledTask> nameTaskMap) {
		//获取任务配置
		LinkedList<LinkedList<String>> taskList = (LinkedList<LinkedList<String>>) config.get("scheduledTask");
		//没有配置数据
		if(taskList==null || taskList.isEmpty()) {
			//抛出异常
			throw new NullPointerException("没有配置任务");
		}
		//遍历解析任务
		taskList.forEach(v->{
			//配置对象
			ScheduledTaskConfig config = new ScheduledTaskConfig();
			//保存值
			config.setJobEcd(v.get(0));
			config.setJobDesc(v.get(1));
			config.setCronExp(v.get(2));
			config.setJobBeanName(v.get(3));
			try {
				//设置参数
				config.setJobParam(v.get(4));
				//加载Cron表达式
				config.setCronBean(new CronBean(config.getCronExp()));
				//关联Bean
				if(StringUtils.isEmpty(config.getJobBeanName())) {
					Logger.warn("定时任务 - 跳过任务“{}”，作业Bean不能为空", config.getJobEcd());
				}else if(!nameTaskMap.containsKey(config.getJobBeanName())) {
					Logger.warn("定时任务 - 跳过任务“{}”，作业Bean不存在：{}", config.getJobEcd(), config.getJobBeanName());
				}else {
					//设置任务Bean
					config.setScheduledTask(nameTaskMap.get(config.getJobBeanName()));
					//存在
					if(taskConfigList.contains(config)) {
						Logger.warn("定时任务 - 跳过任务“{}”，作业编码已存在：{}", config.getJobEcd(), config.getJobDesc());
					}else {
						//保存配置
						taskConfigList.add(config);
					}
				}
			}catch(CronParseException e) {
				Logger.warn("定时任务 - 跳过任务“{}”，配置的cron表达式解析失败：{}", config.getJobEcd(), e.getMessage());
			}catch(JSONParseException e) {
				Logger.warn("定时任务 - 跳过任务“{}”，作业参数格式错误：无法解析的JSON字符串{}", config.getJobEcd(), config.getJobParam());
			}
		});
		//返回任务配置列表
		return taskConfigList.size()>0;
	}

	/**
	 * 定时任务核心调用
	 */
	private static void runCore(int[] timeParts, ScheduledTaskService service) {
		//遍历任务
		taskConfigList.forEach(config->{
			//匹配到时间点，则执行
			if(config.getCronBean().isMatched(timeParts)) {
				//时间点
				long time = Long.parseLong(CronBean.getFormatTime(timeParts));
				//保存状态
				if(useDatabase) {
					//保存任务起始状态
					boolean res = service.saveBegin(config, time);
					//保存失败，则跳过
					if(!res) {
						Logger.info("任务点{}-->{}已存在，自动跳过", config.getJobEcd(), time);
						return;
					}
				}
				//状态信息
				int success = 1; String msg;
				try {
					//执行任务
					config.getScheduledTask().execute(config.getJobParamJson());
					//信息
					msg = "执行成功";
				}catch(Exception e) {
					Logger.error(e);
					success = 0;
					msg = e.getMessage();
				}
				//使用数据库，保存状态
				if(useDatabase) {
					//保存执行结果状态
					service.saveFinished(config, time, success, msg);
				}
			}
		});
	}

	/**
	 * 加载任务Bean
	 * @param taskLocations 任务Bean地址
	 */
	private static Map<String, ScheduledTask> loadTaskBean(ClassLoader classLoader, String taskLocations) {
		//名称和任务Bean映射
		Map<String, ScheduledTask> nameTaskMap = new HashMap<>();
		//日志
		Logger.info("定时任务 - 开始加载定时任务Bean");
		//加载目录并获取TaskBean
		List<String> taskBeanList = new ArrayList<>();
		//处理多地址配置
		for(String taskLocation: taskLocations.split(";")) {
			//查找定时任务Bean
			taskBeanList.addAll(PathUtils.getAllClassNames(classLoader, taskLocation, "[\\w]+ScheduledTask"));
		}
		//遍历
		taskBeanList.forEach(classFullName->{
			//分隔符号
			int dot = classFullName.lastIndexOf(".");
			//获取名称
			String name = dot==-1 ? classFullName : classFullName.substring(dot + 1);
			//反射获取任务Bean
			try {
				ScheduledTask scheduledTask = (ScheduledTask) Class.forName(classFullName).newInstance();
				//保存映射
				nameTaskMap.put(name, scheduledTask);
			} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
				//创建任务失败，输出日志即可
				Logger.error("任务Bean创建失败：Bean名称：{}, 错误原因：{}", classFullName, e.getMessage());
			}
		});
		//检查任务Bean数量，如果没有任务Bean，则不开启定时任务
		return nameTaskMap;
	}
}
